﻿#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//移位操作符
//<< 左移操作符
//>> 右移操作符
//注：移位操作符的操作数只能是整数。
//移动的是二进制位  而且是存储在内存中的二进制位（补码）


//左移操作符：左边抛弃，右边补0
//int main()
//{
//	int a = 10;
//	//00000000000000000000000000001010 -- 10的原码 补码 反码  因为10是正整数 三个码都一样
//   //000000000000000000000000000010100    往左整体移动一位  然后右边补0  
//	//00000000000000000000000000010100 -- b  所以就是20  但是a本身的值不改变
//	int b = a << 1;  // 把a向左移动一位
//	printf("b = %d\n", b);
//	printf("a = %d\n", a);
//	a = a << 1;//这样a的值才会发生改变    a <<= 1也可以
//	return 0;
//}

//int main()
//{
//	int a = -1;
//	//10000000000000000000000000000001 --  -1的原码
//	//11111111111111111111111111111110 --  -1的反码
//	//11111111111111111111111111111111 --  -1的补码
//	//11111111111111111111111111111110 --  左移一位了   b的补码
//	//10000000000000000000000000000010 --   b的原码   补码到原码 取反 +1  符号位不变  也就是-2  a本身也是不发生改变的
//	// 总结左移一位有*2的效果
//	int b = a << 1;
//	printf("b = %d\n", b);
//	printf("a = %d\n", a);
//	return 0;
//}


//右移操作符：
// 1. 逻辑右移：左边⽤0填充，右边丢弃
//2. 算术右移：左边⽤原该值的符号位填充，右边丢弃
//int main()
//{
//	int a = -10;
//	//10000000000000000000000000001010  -- -10的原码
//	//11111111111111111111111111110101  -- -10的反码
//	//11111111111111111111111111110110  -- -10的补码
//	// 11111111111111111111111111110110  -- 这是右移一位
//	//01111111111111111111111111111011   -- 逻辑右移 左边补0右边丢弃
//	//11111111111111111111111111111011   -- 算术右移  左边用原该值的符号位填充，右边丢弃
//	// 右移到底采用算术右移还是逻辑右移是取决于编译器，一般采用的都是算术右移
//	//11111111111111111111111111111011   -- 算术右移  左边用原该值的符号位填充，右边丢弃  补码
//	//10000000000000000000000000000101   -- 原码   补码 -> 原码 取反＋1  所以这个数就-5 b = -5
//	int b = a >> 1;
//	printf("a = %d\n", a); // a本身不改变还是-10
//	printf("b = %d\n", b); //-5
//}

//警告⚠️：对于移位运算符，不要移动负数位，这个是标准未定义的。 a>>-3这是不行的

//位操作符：& 、 | 、 ^ 、~  操作数必须是整数    &&逻辑于 ||逻辑或  关注的是真假   & | 是按2进制位 
//& 按位与
//| 按位或
//
//^ 按位异或
//~ 按位取反
//int main()
//{
//	int a = 6;
//	//00000000000000000000000000000110  -- 6的原反补码  因为是正整数 都相同
//
//	int b = -7;
//	//10000000000000000000000000000111 --  -7的原码
//	//11111111111111111111111111111000 --  -7的反码
//	//11111111111111111111111111111001 --  -7的补码 
//	int c = a & b; // a和b的补码的二进制位进行计算  对应的2进制位按位于  同时为1才为1  有一个为0 则为0
//	//00000000000000000000000000000110 --   6的补码
//	//11111111111111111111111111111001 --  -7的补码
//	//00000000000000000000000000000000 --   结果是0  
//	printf("c = %d", c);
//	return 0;
//}

//int main()
//{
//	int a = 6;
//	//00000000000000000000000000000110  -- 6的原反补码  因为是正整数 都相同
//
//	int b = -7;
//	//10000000000000000000000000000111 --  -7的原码
//	//11111111111111111111111111111000 --  -7的反码
//	//11111111111111111111111111111001 --  -7的补码 
//	int c = a | b; // a和b的补码的二进制位进行计算  对应的2进制位按位或  只要有1就是1 两个同时为0 才为0
//	//00000000000000000000000000000110 --   6的补码
//	//11111111111111111111111111111001 --  -7的补码
//	//11111111111111111111111111111111 --   现在是补码 而且是负数 所以我们要取反+1变为原码
//	//10000000000000000000000000000001 --   所以就是-1
//	printf("c = %d", c);
//	return 0;
//}

//int main()
//{
//	int a = 6;
//	//00000000000000000000000000000110  -- 6的原反补码  因为是正整数 都相同
//
//	int b = -7;
//	//10000000000000000000000000000111 --  -7的原码
//	//11111111111111111111111111111000 --  -7的反码
//	//11111111111111111111111111111001 --  -7的补码 
//	int c = a ^ b; // a和b的补码的二进制位进行计算  对应的2进制位按位异或   对应的二进制为，相同为0，相异为1
//	//00000000000000000000000000000110 --   6的补码
//	//11111111111111111111111111111001 --  -7的补码
//	//11111111111111111111111111111111 --   这是补码  取反+1转换为原码后 所以就是-1
//	printf("c = %d", c);
//	return 0;
//}

//int main()
//{
//	int a = 0;
//	printf("%d\n", ~a); // ~按2进制位取反  这是单目操作符   
//	//00000000000000000000000000000000  0的原反补码
//	//11111111111111111111111111111111   补码  取反 +1变为原码
//	//10000000000000000000000000000001   所以也就是-1
//	return 0;
//}

//不能创建临时变量（第三个变量），实现两个整型变量的交换
//int main()
//{
//	int a = 3;
//	int b = 5;
//	printf("交换前:a=%d b=%d\n", a, b);
//	a = a + b;
//	b = a - b;
//	a = a - b;
//	printf("交换前:a=%d b=%d\n", a, b);
//	return 0;
//}
//这个算法的缺陷是a和b如果相加超过整数的上限就不能这样了


//int main()
//{
//	int a = 3;
//	int b = 5;
//	printf("交换前:a=%d b=%d\n", a, b);
//	a = a ^ b;
//	b = a ^ b;  // a^b^b 所以b=a
//	a = a ^ b;  // a^b^a  所以a=b
//	printf("交换前:a=%d b=%d\n", a, b);
//	return 0;
//}

// int a = 3  int b - 5;  异或操作符的特点   相同为0 相异为1
// 3^3=0 
// a^a=0 
// 0^3 = 3    0：000  3：011  前面没写的都是0 所以0^3=011 -- 3
//0^a = a
//所以3^3^5 = 5   
//3^5^3 =     3:011  5:101   3^5=110  3^5^3=101也就是5
//所以异或是支持交换律的  而且异位操作符不会进位 所以不存在溢出


//练习1：编写代码实现：求⼀个整数存储在内存中的⼆进制中1的个数
//能不能拿到二进制的每一位然后统计？ 比如7  7%2 ->  111  7/2 =3  3 -- 11  3%2 = 1  3/2 =1
//int count_bit_one(unsigned int n)
//{
//	int count = 0;
//	while (n) // n不为0说明2进制位里面一定有1
//	{
//		if ((n % 2) == 1)
//		{
//			count++;
//		}
//		n /= 2;
//	}
//	return count;
//}
////11111111111111111111111111111111  -1存在内存里面全是1 补码  我们这个程序算不出-1中1的个数
//// 我们可以弄成unsigned int 无符号整数  这样就可以了
//int main()
//{
//	int num = 0;
//	scanf("%d", &num);
//	int ret = count_bit_one(num);
//	printf("%d\n", ret);
//	return 0;
//}


// 给n&1
//比如n 11111111111111111111111111111111
//    1 00000000000000000000000000000001
//  n&1 00000000000000000000000000000001  如果n&1 == 1 知道他的最低位是1  n&1 == 0说明最低位是0
// 然后我们一直n右移1,2,3,4位 一直循环
//int count_bit_one(int n)
//{
//	int i = 0;
//	int count = 0;
//	for (i = 0; i < 32; i++)
//	{
//		if (((n >> i) & 1)  == 1)
//		{
//			count++;
//		}
//	}
//	return count;
//}

// n = 11  n = n &（n-1）  这个算法把n二进制序列中最右边的1去掉了
//     n -- 1011   n-1 -- 1010  
// 所以n -- 1010   n - 1 -- 1001  
// 所以n -- 1000   n - 1 -- 0111
// 所以n -- 0000  我们发现每一次运算都把最右边的1弄掉
// 或者这种算法
//int count_bit_one(int n)
//{
//	int i = 0;
//	int count = 0;
//	while (n) // n变为0就没有1了 就不在循环
//	{
//		n = n & (n - 1);
//		count++;
//	}
//	return count;
//}
//
//int main()
//{
//	int num = 0;
//	scanf("%d", &num);
//	int ret = count_bit_one(num);
//	printf("%d\n", ret);
//	return 0;
//}

// 写一个代码，判断n是否是2的次方数

// if( (n&(n-1)) == 0)   说明n就是2的次方数

//练习2：⼆进制位置0或者置1
//编写代码将13⼆进制序列的第5位修改为1，然后再改回0
//13的2进制序列： 00000000000000000000000000001101    13
//将第5位置为1后：00000000000000000000000000011101    29
//将第5位再置为0：00000000000000000000000000001101

//int main()
//{
//	int a = 13;
//	// 00000000000000000000000000001101 -- 13
//	// 00000000000000000000000000010000 -- 只要|下面这个数 有1则为1  相同才为0  或
//
//	// 00000000000000000000000000011101 -- 29
//	// 11111111111111111111111111101111 -- 只要&上这个数即可 就能变为13 这个数是我们刚才得到的那个数取反
//	// 2进制位按位于  同时为1才为1  有一个为0 则为0
//	int n = 5;
//	   // 1<<(n-1)这样就能得到上面的第二个数   左移动4位
//	a = a | (1 << (n - 1));  
//	printf("%d\n", a);  // 29
//	a &= ~(1 << (n - 1));
//	printf("%d\n", a);  // 13
//
//	return 0;
//}

//！、++、--、 & 、 * 、 + 、 - 、~、sizeof、(类型)
// & -- 取地址操作符  * -- 解引用操作

//exp1, exp2, exp3, …expN
//逗号表达式，就是⽤逗号隔开的多个表达式。
//逗号表达式，从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。
// 一定要从左往右依次计算  前面表达式计算可能影响后面的计算
//int main()
//{
//	int a = 1;
//	int b = 2;
//	int c = (a > b, a = b + 10, a, b = a + 1);  // 符号表达式
//	printf("%d\n", c); // 13
//	return 0;
//}

//int main()
//{
//	int a = 0;
//	int b = 0;
//	int c = 0;
//	int d = 0;
//
//	if (a = b + 1, c = a / 2, d > 0) // 最后表达式结果是假 所以为0
//	{
//
//	}
//	return 0;
//}

//int main()
//{
//	int a = 7;
//	//a = get_val();
//	//count_val(a);
//	//while (a > 0)
//	//{
//	//	//业务处理
//	//	a = get_val();
//	//	count_val(a);
//	//}
//
//	//如果使⽤逗号表达式，改写：
//	while (a = get_val(), count_val(a), a > 0)
//	{
//			//业务处理
//	}
//}

//[] 下标引⽤操作符
//操作数：⼀个数组名 + ⼀个索引值
//int arr[10];//创建数组
//arr[9] = 10;//实⽤下标引⽤操作符。
//[] 的两个操作数是arr和9。

//int main()
//{
//	int arr[10] = { 1,2,3,4,5 };
//
//	int m = arr[4]; // 数组中下标是4的元素  []下标引用操作符 - 操作数是: arr,4
//	printf("%d ", m);
//	return 0;
//}

//int Add(int x, int y)
//{
//	return x + y;
//}
//int main()
//{
//	printf("hehe\n");  //()就是函数调用操作符
//	printf("%d\n", 66); // 函数调用操作符最少有1个操作数 函数名字
//	int ret = Add(3, 5);  // 操作数是:Add 3 5
//}
//
//接受⼀个或者多个操作数：第⼀个操作数是函数名，剩余的操作数就是传递给函数的参数。

//C语⾔已经提供了内置类型，如：char、short、int、long、float、double等，但是只有这些内置类
//型还是不够的，假设我想描述学⽣，描述⼀本书，这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要
//名字、年龄、学号、⾝⾼、体重等；描述⼀本书需要作者、出版社、定价等。C语⾔为了解决这个问
//题，增加了结构体这种⾃定义的数据类型，让程序员可以⾃⼰创造适合的类型。
//📌 结构是⼀些值的集合，这些值称为成员变量。结构的每个成员可以是不同类型的变量，如：
//标量、数组、指针,甚至是其他的结构体

// 结构体的声明 关键字 - struct 结构体关键字

//struct tag  // 自定义名字
//{
//	member - list; 成员列表
//}; variable - list;  // 变量列表

// 学生类型
//struct Student
//{
//	// 成员变量
//	char name[20];
//	int age;
//	float score;
//}s4，s5，s6; // 这几个也是我们创建的变量，全局变量
//
//struct Student s3 = {"小果叮",18,100.0}; // 全局变量
//struct Point
//{
//	int x;
//	int y;
//};
//
//struct S
//{
//	char ch;
//	struct Point p;
//	int arr[10];
//	double d;
//};
//int main()
//{
//	int a;
//	struct Student s1 = {"十五",1,100.0};  // s1同学 局部变量
//	struct Student s2 = {"福宝",1,99.0};  // s2同学
//
//	struct S s = { 'a',{1,5},{1,2,3,4,5,6,7,8},3.14 };  // 初始化
//
//	printf("%c\n", s.ch); //a
//	printf("%d %d\n", s.p.x, s.p.y); // 这样就能拿出结构体里面的东西
//	printf("%d\n", s.arr[0]);
//	printf("%lf\n", s.d);
//	return 0;
//}
//结构体成员的直接访问是通过点操作符（.）访问的。点操作符接受两个操作数。
//使⽤⽅式：结构体变量.成员名

//有时候我们得到的不是⼀个结构体变量，⽽是得到了⼀个指向结构体的指针。
//使⽤⽅式：结构体指针->成员名

//C语⾔的操作符有2个重要的属性：优先级、结合性，这两个属性决定了表达式求值的计算顺序。
// 相邻的操作符，优先级高的先执行
// 相邻操作符的优先级相同的情况下，结合性说的算
//int main()
//{
//	int r = 3 + 4 * 5;  // 乘法优先级更高
//	return 0;
//}

// 当我们明确了优先级和结合性，那是否就能确定一个表达式的计算结果呢？
// 其实可能会有一些地方确定不了

//整型提升
//C语⾔中整型算术运算总是⾄少以缺省整型类型的精度来进⾏的。
//为了获得这个精度，表达式中的字符和短整型操作数在使⽤之前被转换为普通整型，这种转换称为整
//型提升。
//整型提升的意义：
//表达式的整型运算要在CPU的相应运算器件内执⾏，CPU内整型运算器(ALU)的操作数的字节⻓度⼀
//般就是int的字节⻓度，同时也是CPU的通⽤寄存器的⻓度。
//因此，即使两个char类型的相加，在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓
//度。
//通⽤CPU（general - purpose CPU）是难以直接实现两个8⽐特字节直接相加运算（虽然机器指令中
//可能有这种字节相加指令）。所以，表达式中各种⻓度可能⼩于int⻓度的整型值，都必须先转换为
//int或unsigned int，然后才能送⼊CPU去执⾏运算。
//int main()
//{
//	//int a = 10;
//	//int b = 20;
//	//int c = a + b;
//	char a = 20;
//	//00000000000000000000000000010100  -- 20的原码
//	//00010100 - a   char类型是1字节  8比特位
//
//	char b = 130;
//	//00000000000000000000000010000010 -- 130的原码
//	//10000010 - b  char类型是1字节 8比特位  char 是字符类型 是否是signed char 不一定     是取决是编译器 在vs上 char == signed char
//	// int 整型类型 是signed int 有符号的int
//	char c = a + b;
//	//00010100 - a   所以0是a的符号位
//	//00000000000000000000000000010100 - a   提升为整型  在前面补上符号位
//	//10000010 - b   所以1是b的符号为
//	//11111111111111111111111110000010  - b
//	//11111111111111111111111110010110 -  a + b  这是补码 放到char中 只存8个比特位 然后再截断
//	// 10010110 - c  补码  %d打印有符号的整数
//	//11111111111111111111111110010110 - c  提升后c的补码  再转换为原码
//	//10000000000000000000000001101010 - 这就是-106
//	printf("%d\n", c);  // a b 被提升为普通整型，然后再执⾏加法运算。
//	return 0;
//}
// char类型的取值范围是-128到127
//如何进⾏整体提升呢？
//1. 有符号整数提升是按照变量的数据类型的符号位来提升的
//2. ⽆符号整数提升，⾼位补0

//负数的整形提升
//char c1 = -1;
//变量c1的⼆进制位(补码)中只有8个⽐特位：
//1111111
//因为 char 为有符号的 char
//所以整形提升的时候，⾼位补充符号位，即为1
//提升之后的结果是：
//11111111111111111111111111111111
////正数的整形提升
//char c2 = 1;
//变量c2的⼆进制位(补码)中只有8个⽐特位：
//00000001
//因为 char 为有符号的 char
//所以整形提升的时候，⾼位补充符号位，即为0
//提升之后的结果是：
//00000000000000000000000000000001
//⽆符号整形提升，⾼位补0
// 整型提升讨论的是：表达式中char和short类型的值

//算术转换：讨论的是类型大于等于整型的类型的类型
//如果某个操作符的各个操作数属于不同的类型，那么除⾮其中⼀个操作数的转换为另⼀个操作数的类
//型，否则操作就⽆法进⾏。下⾯的层次体系称为寻常算术转换。
//long double
//double
//float
//unsigned long int
//long int
//unsigned int
//int
//如果某个操作数的类型在上⾯这个列表中排名靠后，那么⾸先要转换为另外⼀个操作数的类型后执⾏
//运算。

// int ; double d;  a+d； 会把int转换为double类型的然后再计算

//a* b + c * d + e * f
//表达式1在计算的时候，由于* ⽐ + 的优先级⾼，只能保证， * 的计算是⽐ + 早，但是优先级并不
//能决定第三个 * ⽐第⼀个 + 早执⾏。

//c + --c;
//同上，操作符的优先级只能决定⾃减 -- 的运算在 + 的运算的前⾯，但是我们并没有办法得知， + 操
//作符的左操作数的获取在右操作数之前还是之后求值，所以结果是不可预测的，是有歧义的
//int main()
//{
//	int i = 10;
//	i = i-- - --i * (i = -3) * i++ + ++i;
//	printf("i = %d\n", i);
//	return 0;
//}

#include <stdio.h>
//int fun()
//{
//	static int count = 1;
//	return ++count;
//}
//int main()
//{
//	int answer;
//	answer = fun() - fun() * fun();
//	printf("%d\n", answer);//输出多少？
//	return 0;
//}
//这个代码有没有实际的问题？有问题！
//虽然在⼤多数的编译器上求得结果都是相同的。
//但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知：先
//算乘法，再算减法。
//函数的调⽤先后顺序⽆法通过操作符的优先级确定。

#include <stdio.h>
//int main()
//{
//	int i = 1;
//	int ret = (++i) + (++i) + (++i);
//	printf("%d\n", ret);
//	printf("%d\n", i);
//	return 0;
//}
//这段代码中的第⼀个 + 在执⾏的时候，第三个++是否执⾏，这个是不确定的，因为依靠操作符的优先
//级和结合性是⽆法决定第⼀个 + 和第三个前置 ++ 的先后顺序。
//即使有了操作符的优先级和结合性，我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的
//计算路径，那这个表达式就是存在潜在⻛险的，建议不要写出特别负责的表达式。

//我们知道计算上CPU（中央处理器）在处理数据的时候，需要的数据是在内存中读取的，处理后的数
//据也会放回内存中，那我们买电脑的时候，电脑上内存是8GB / 16GB / 32GB等，那这些内存空间如何⾼
//效的管理呢？
//其实也是把内存划分为⼀个个的内存单元，每个内存单元的⼤⼩取1个字节。
//计算机中常⻅的单位（补充）：
//⼀个⽐特位可以存储⼀个2进制的位1或者0

//其中，每个内存单元，相当于⼀个学⽣宿舍，⼀
//个⼈字节空间⾥⾯能放8个⽐特位，就好⽐同学们
//住的⼋⼈间，每个⼈是⼀个⽐特位。
//每个内存单元也都有⼀个编号（这个编号就相当
//于宿舍房间的⻔牌号），有了这个内存单元的编
//号，CPU就可以快速找到⼀个内存空间。

//⽣活中我们把⻔牌号也叫地址，在计算机中我们
//把内存单元的编号也称为地址。C语⾔中给地址起
//了新的名字叫：指针。
//所以我们可以理解为：
//内存单元的编号 == 地址 == 指针

// 内存的使用和管理
//1.内存划分为一个个的内存单元，每个内存单元的大小是1个字节    计算机单位:bit - 比特位  Byet - 字节  KB MB GB TB
//一个比特位可以存储一个2进制的1或0   1Byte = 8bit         

 //取地址操作符（&）     
//int main()
//{
//	int a = 20; // 变量创建的本质其实是在内存中申请空间
//	//向内容申请4个字节的空间 int类型的长度是4个字节  用来存放20这个数值
//	// 调试窗口 内存 取地址之后 显示的是16进制
//	// 这个四个字节 每个字节都有编号（地址）
//	// 我们会发现有4个地址 并且是连续的
//	// 变量的名字仅仅是给程序员看的 编译器不看名字 是通过地址找内存单元的
//	return 0;
// }

//int main()
//{
//	int a = 20;  // &a可以得到a的地址   & 取地址操作符
//	printf("%p", &a);  // %p是用来打印地址的  我们调试可以发现打印出来取的是较小的地址
//	// 也就是四个字节中第一个字节的地址  是16进制位的
//
//	int *  pa = &a; // pa是一个变量，这个变量是用来存放地址(指针)的,pa叫指针变量  pa的类型是int*  
//	// pa是指针变量的名字  * 表示pa是指针变量  int 表示pa指向的变量a的类型是int
//	// 同理 char ch = 'z'  char * pc = &ch
//	return 0;
//}

//* pa 的意思就是通过pa中存放的地址，找到指向的空间，
//* pa其实就是a变量了

//int main()
//{
//	int a = 20;  
//	int* pa = &a;  
//	*pa; // * - 解引用操作符（间接访问操作符） *pa其实就是a
//	*pa = 30;
//	printf("%d", a); // 30
//	return 0;
//}

// a&b -- 按位于   &a -- 取地址    a*b -- 乘法    *a a是指针变量，*就是解引用操作符

//前⾯的内容我们了解到，32位机器假设有32根地址总线，每根地址线出来的电信号转换成数字信号后
//是1或者0，那我们把32根地址线产⽣的2进制序列当做⼀个地址，那么⼀个地址就是32个bit位，需要4
//个字节才能存储。
//如果指针变量是⽤来存放地址的，那么指针变的⼤⼩就得是4个字节的空间才可以。
//同理64位机器，假设有64根地址线，⼀个地址就是64个⼆进制位组成的⼆进制序列，存储起来就需要
//8个字节的空间，指针变的⼤⼩就是8个字节

//32位平台下地址是32个bit位，指针变量⼤⼩是4个字节
//64位平台下地址是64个bit位，指针变量⼤⼩是8个字节
//注意指针变量的⼤⼩和类型是⽆关的，只要指针类型的变量，在相同的平台下，⼤⼩都是相同的。

//int main()
//{
//	int a = 10;
//	int* p = &a ;
//	// 1.指针变量是用来存放地址的，地址的存放需要多大空间，那么指针变量的大小就是多大
//	printf("%zd\n", sizeof(p));  // x86就是32位环境  x64是64位环境
//
//	char ch = 'w';
//	char* pc = &ch;
//	printf("%zd\n", sizeof(pc)); //
//	printf("%zd\n", sizeof(char*));
//	printf("%zd\n", sizeof(short*));
//	printf("%zd\n", sizeof(int*));
//	printf("%zd\n", sizeof(double*));
//	return 0;
//}

// 那这样指针类型有什么意思呢  为什么还存在这么多指针类型
// 代码1
//int main()
//{
//	int a = 0x11223344; // 一个16进制占两个2进制位    两个16进制占8个2进制位 也就是1个字节 所以这里就占满了4个字节
//	int* pa = &a;
//	*pa = 0;
//	return 0;
//}

//  代码2
//int main()
//{
//	int a = 0x11223344;
//	char* pa = &a;
//	*pa = 0;
//	return 0;
//}
//调试我们可以看到，代码1会将a的4个字节全部改为0，但是代码2只是将a的第⼀个字节改为0。
//结论：指针的类型决定了，对指针解引⽤的时候有多⼤的权限（⼀次能操作⼏个字节）。
//⽐如： char* 的指针解引⽤就只能访问⼀个字节，⽽ int* 的指针的解引⽤就能访问四个字节。

#include <stdio.h>
//int main()
//{
//	int a = 20;
//	int* pa = &a;
//	char* pc = &a;
//
//	printf("&a = %p\n", &a);
//	printf("pa = %p\n", pa);
//	printf("pc = %p\n", pc); // 这三个值是一样的
//
//	printf("&a+1 = %p\n", &a+1);
//	printf("pa+1 = %p\n", pa+1); 
//	printf("pc +1= %p\n", pc+1);
//	return 0;
//}

//调试 我们可以看出， char* 类型的指针变量 + 1跳过1个字节， int* 类型的指针变量 + 1跳过了4个字节。
//这就是指针变量的类型差异带来的变化。
//结论：指针的类型决定了指针向前或者向后⾛⼀步有多⼤（距离）。
//int* pa; pa + 1 -- > +1 * sizeof(int)
//		 pa + n-- > +n * sizeof(int)
//char* pc; pc + 1 -->1*sizeof(char)	
//          pc + n -->n*sizeof(char)

//在上⾯的代码中，将⼀个int类型的变量的地址赋值给⼀个char* 类型的指针变量。编译器给出了⼀个警
//告（ 从“int *”到“char *”的类型不兼容），是因为类型不兼容。⽽使⽤void* 类型就不会有这样的问题。
//在指针类型中有⼀种特殊的类型是 void* 类型的，可以理解为⽆具体类型的指针（或者叫泛型指
//针），这种类型的指针可以⽤来接受任意类型地址。但是也有局限性， void* 类型的指针不能直接进
//⾏指针的 + -整数和解引⽤的运算

//int main()
//{
//	int a = 10;
//	//int* pa = &a;
//	//char* pc = &a;
//	char ch = 'w';
//	void* pv1 = &a;  //int*
//	void* pv2 = &ch; //char*
//
//	*pv1 = 100;
//	*pv2 = 1000;  // 就会报错
//	return 0;
//}

//这⾥我们可以看到， void* 类型的指针可以接收不同类型的地址，但是⽆法直接进⾏指针运算。
//那么 void* 类型的指针到底有什么⽤呢？
//⼀般 void* 类型的指针是使⽤在函数参数的部分，⽤来接收不同类型数据的地址，这样的设计可以
//实现泛型编程的效果。使得⼀个函数来处理多种类型的数据，